package com.sztx.pay.center.core.service.impl;

import java.sql.Timestamp;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;

import com.sztx.pay.center.common.util.DateUtil;
import com.sztx.pay.center.common.util.StringUtil;
import com.sztx.pay.center.common.util.ValidateUtil;
import com.sztx.pay.center.core.service.BillnoHelperService;
import com.sztx.pay.center.core.service.bean.BillnoModel;
import com.sztx.pay.center.core.service.bean.MutiKey;
import com.sztx.pay.center.dataaccess.dao.mysql.CommonIdInstanceMysqlDAO;
import com.sztx.pay.center.dataaccess.dao.mysql.CommonIdTemplateMysqlDAO;
import com.sztx.pay.center.dataaccess.domain.CommonIdInstanceDO;
import com.sztx.pay.center.dataaccess.domain.CommonIdTemplateDO;
import com.sztx.se.common.exception.BusinessException;
import com.sztx.se.core.service.impl.BaseServiceImpl;

@Service
public class BillnoHelperServiceImpl extends BaseServiceImpl implements BillnoHelperService, InitializingBean {

	private Logger logger = LoggerFactory.getLogger(BillnoHelperServiceImpl.class);
	
	private Map<MutiKey, BillnoModel> cache = new ConcurrentHashMap<MutiKey, BillnoModel>();
	
	private ReentrantLock lock = new ReentrantLock();
	
	private long delay = 120000l;
	
	public static final String DATE_STYLE = "yyyyMMdd";
	
	@Autowired
	private CommonIdInstanceMysqlDAO commonIdInstanceMysqlDAO;
	
	@Autowired
	private CommonIdTemplateMysqlDAO commonIdTemplateMysqlDAO;
	
	
	
	
	/**
	 * 
	 * @Title: getValue
	 * @Description: 
	 * @param @param key
	 * @param @param date
	 * @param @return    设定文件
	 * @return Map<String,Object>    返回类型
	 * @throws
	 */
	public BillnoModel getValue(int key, String date) {
		
		Date nowDate = checkParams(key, date);
		date = DateUtil.formatDate(nowDate, DATE_STYLE);
		MutiKey mutikey = new MutiKey(key, date);
		
		return getCacheData(mutikey, nowDate);
	}
	
	/**
	 * 
	 * @Title: getData
	 * @Description: 支持多线程获取
	 * @param @param key
	 * @param @return    设定文件
	 * @return Object    返回类型
	 * @throws
	 */
	private BillnoModel getCacheData(MutiKey key, Date date){
		BillnoModel value = null;
		value = cache.get(key);
		if(value == null || value.isneed()){
			try{
				lock.lock();
				value = cache.get(key);
				if(value==null || value.isneed()){
					value = queryDb(key, date);
					cache.put(key, value);
				}
			}finally{
				lock.unlock();
			}
		}
		return value;
	}
	
	/**
	 * 
	 * @Title: checkParams
	 * @Description: 校验参数
	 * @param @param key
	 * @param @param date    设定文件
	 * @return void    返回类型
	 * @throws
	 */
	private Date checkParams(int key, String date) {
		
		ValidateUtil.isBetween(key, 1000, 9999, "templateId必须为4位整数");
		
		
		Date nowDate = new Date();
		if (StringUtil.isNotEmpty(date)) {			
			ValidateUtil.isDate(date, DATE_STYLE, "传入的日期" + date + "格式有误");
			
			try {
				nowDate = DateUtil.parse(date, DATE_STYLE);
			} catch (Exception e) {
				throw new RuntimeException("根据传入日期" + date + "解析为DATE对象异常!异常信息如下:\n" + e.getMessage());
			}			
		}
		return nowDate;
	}
	
	/**
	 * 查询数据库
	 * @Title: queryDb
	 * @Description: 
	 * @param @return    设定文件
	 * @return Object    返回类型
	 * @throws
	 */
	private BillnoModel queryDb(MutiKey key, Date date) {
		BillnoModel model = null;
		
		try {
			model = processObj(key, date);
		}catch(Exception e) {
			logger.error("获取billno流水失败了!异常信息如下:\n", e);
			if(e instanceof DuplicateKeyException) {
				logger.error("主键冲突!重新获取,不影响生成单号!");
				model = processObj(key, date);
			}else {
				throw new RuntimeException(e);
			}
		}
		
		return model;
	}
	

	
	/**
	 * 
	 * @Title: doRun
	 * @Description:定时任务运行
	 * @param     设定文件
	 * @return void    返回类型
	 * @throws
	 */
	public void doRun() {
		logger.info("do run");
		Calendar calendar = Calendar.getInstance();
		calendar.set(Calendar.HOUR_OF_DAY, 17);
		calendar.set(Calendar.MINUTE, 19);
		calendar.set(Calendar.SECOND, 0);
		Date date = calendar.getTime();
		if (date.before(new Date())) {
			date = this.addDay(date, 1);
		}
		Timer timer = new Timer();
		ClearCacheThread task = new ClearCacheThread();
		timer.schedule(task, date, delay);
	}

	public Date addDay(Date date, int num) {
		Calendar startDT = Calendar.getInstance();
		startDT.setTime(date);
		startDT.add(Calendar.DAY_OF_MONTH, num);
		return startDT.getTime();
	}

	private class ClearCacheThread extends TimerTask {

		private Logger logger = LoggerFactory.getLogger(ClearCacheThread.class);

		@Override
		public void run() {
			try {
				logger.info("clear cache");
				Date date = new Date();
				Date beforeDate = addDay(date, -1);
				String bdateStr = DateUtil.formatDate(beforeDate, DATE_STYLE);
				Date bdate = DateUtil.parserStringToDate(bdateStr + " 23:59:59", "yyyyMMdd HH:mm:ss");
				for(Map.Entry<MutiKey, BillnoModel> entry : cache.entrySet()) {
					BillnoModel model = entry.getValue();
					if(1 != model.getType()) {
						MutiKey key = entry.getKey();
						String strDate = key.getDate();
						Date cdate = DateUtil.parserStringToDate(strDate + " 00:00:00", "yyyyMMdd HH:mm:ss");
						if(cdate.before(bdate)) {
							cache.remove(key);
						}
					}
				}
			} catch (Exception e) {
				logger.info("billno清理缓存定时任务发生异常，异常信息如下：\n" + e.getMessage());
				return;
			}
		}
	}


	
	private BillnoModel processObj(final MutiKey key, Date date) {
		final Timestamp ctime = new Timestamp(date.getTime());
		
		BillnoModel model = null;
		
		
		//1.查找实例
		CommonIdInstanceDO instance = commonIdInstanceMysqlDAO.findById(key.getKey(), key.getDate());
		
		if (instance == null) {
			
			CommonIdTemplateDO template = commonIdTemplateMysqlDAO.findById(key.getKey());
			if (template == null) {
				throw new BusinessException(key+"模板未定义");
			}else {
				int continueType = template.getContinueType();
				
				CommonIdInstanceDO newInstance = new CommonIdInstanceDO();
				newInstance.setContinueType(continueType);
				
				if (continueType == 1) { // 跨天连续
					CommonIdInstanceDO lastInstance = commonIdInstanceMysqlDAO.findLastModify(key.getKey());
					
					if (lastInstance != null) { //已有实例数据
						model = updateObj(lastInstance);
					}else {	//新增一条实例数据					
						newInstance.setBufferLength(template.getBufferLength());
						newInstance.setCreateTime(key.getDate());
						newInstance.setMaxValue(template.getMaxValue());
						newInstance.setModifyTime(ctime);
						newInstance.setOrderStyle(template.getOrderStyle());
						newInstance.setStartSeq(template.getStartSeq());
						newInstance.setState(1);
						newInstance.setTemplateId(template.getTemplateId());
						newInstance.setRemark("");
						newInstance.setPrefix(template.getPrefix());
						
						model = saveObj(newInstance);
					}
				}else { //跨天非连续				
					newInstance.setBufferLength(template.getBufferLength());
					newInstance.setCreateTime(key.getDate());
					newInstance.setMaxValue(template.getMaxValue());
					newInstance.setModifyTime(ctime);
					newInstance.setOrderStyle(template.getOrderStyle());
					newInstance.setStartSeq(template.getStartSeq());
					newInstance.setState(1);
					newInstance.setTemplateId(template.getTemplateId());
					newInstance.setRemark("");
					newInstance.setPrefix(template.getPrefix());
					
					model = saveObj(newInstance);
				}
			}						
		}else {		
			int continueType = instance.getContinueType();		
			Date mDate = instance.getModifyTime();

			if (1 != continueType) {
				if (DateUtil.isBeforeDate(mDate, date)){					
					instance.setStartSeq(0);
					instance.setCreateTime(key.getDate());
					
					model = saveObj(instance);
				}else {
					model = updateObj(instance);
				}				
			}else {
				model = updateObj(instance);			
			}
		}
				
		return model;
	}
	
	
	
	public BillnoModel updateObj(CommonIdInstanceDO instance){	
		BillnoModel model = null;
		
		int templateId = instance.getTemplateId();
		String createTime = instance.getCreateTime();
		
		Long startSeq = Long.parseLong(String.valueOf(instance.getStartSeq()));
		Long maxValue = Long.parseLong(String.valueOf(instance.getMaxValue()));
		Integer bufferLength = Integer.valueOf(String.valueOf(instance.getBufferLength()));
		final Long newStartSeq = startSeq + bufferLength;
		
		if (newStartSeq >= maxValue) {			
			throw new BusinessException("元数据id为["+templateId+"]流水号已用完");		
		}else {			
			commonIdInstanceMysqlDAO.updateStartSeq(String.valueOf(newStartSeq), templateId, createTime);
			
			model = new BillnoModel();
			model.setCurrentSeq(startSeq);
			model.setStartSeq(startSeq);
			model.setMaxSeq(newStartSeq);
			model.setDate(createTime);
			model.setStep(1);
			model.setType(instance.getContinueType());
			model.setPrefix(instance.getPrefix());
			model.setOrderStyle(instance.getOrderStyle());
		}
		
		return model;
	}
	
	
	
	public BillnoModel saveObj(CommonIdInstanceDO instance){
		BillnoModel model = null;
		
		int templateId = instance.getTemplateId();
		String createTime = instance.getCreateTime();
		
		Integer startSeq = Integer.parseInt(String.valueOf(instance.getStartSeq()));
		Integer maxValue = Integer.parseInt(String.valueOf(instance.getMaxValue()));
		Integer bufferLength = Integer.valueOf(String.valueOf(instance.getBufferLength()));
		Integer newStartSeq = startSeq + bufferLength;
		
		if (newStartSeq >= maxValue) {			
			throw new BusinessException("元数据id为["+templateId+"]流水号已用完");		
		}else {
			
			instance.setStartSeq(newStartSeq);
			commonIdInstanceMysqlDAO.save(instance);
			
			model = new BillnoModel();
			model.setCurrentSeq(startSeq);
			model.setStartSeq(startSeq);
			model.setMaxSeq(newStartSeq);
			model.setDate(createTime);
			model.setStep(1);
			model.setType(instance.getContinueType());
			model.setPrefix(instance.getPrefix());
			model.setOrderStyle(instance.getOrderStyle());
		}
		
		return model;	
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		doRun();
	}

	public void setCommonIdInstanceMysqlDAO(
			CommonIdInstanceMysqlDAO commonIdInstanceMysqlDAO) {
		this.commonIdInstanceMysqlDAO = commonIdInstanceMysqlDAO;
	}

	public void setCommonIdTemplateMysqlDAO(
			CommonIdTemplateMysqlDAO commonIdTemplateMysqlDAO) {
		this.commonIdTemplateMysqlDAO = commonIdTemplateMysqlDAO;
	}
	
	
	
}
